package com.leyou.service;

import com.leyou.common.auth.entity.Payload;
import com.leyou.common.auth.entity.UserInfo;
import com.leyou.common.auth.utils.JwtUtils;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exceptions.LyException;
import com.leyou.common.utils.CookieUtils;
import com.leyou.config.JwtProperties;
import com.leyou.config.UserTokenProperties;
import com.leyou.dto.UserDTO;
import com.leyou.user.client.UserClient;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * @author: guojy
 * @date: 2020/3/18 18:53
 * @Description:
 * @version: 1.0
 */
@Service
public class AuthService {
    @Autowired
    private UserClient userClient;
    @Autowired
    private JwtProperties jwtProperties;
    @Autowired
    private UserTokenProperties userTokenProperties;

    // TODO 登录验证 + 授权
    public void login(String username, String password, HttpServletResponse response) {
        // 远程调用，获取用户数据
        UserDTO userDTO = null;
        try {
            userDTO = userClient.queryUserByUsernameAndPassword(username, password);
        } catch (Exception e) {
            throw new LyException(ExceptionEnum.INVALID_PHONE_NUMBER);
        }
        // 根据私钥生成 token
        UserInfo userInfo = new UserInfo(userDTO.getId(), username, "guest");
        createToken(response, userInfo);
    }


    /**
     * TODO 请求鉴权
     * 已知我们已经设置了 token的失效时间，所以无论用户有什么操作，到时间 token自动失效，需要重新登录
     * 但是如果用户一直在浏览网站，突然失效了，要求重新登录，这显然不合适，所以需要解决
     * 方案一：每次用户请求，都会再生成一个token，理论永不过期，缺点，浪费资源
     * 可行方案二：用户请求时，如果token即将过期，则为其重新生成一个 token，比如发现还有 5分钟过期，就重新生成一个
     */
    public UserInfo verify(HttpServletRequest request, HttpServletResponse response) {
        System.out.println("请求鉴权");
        try {
            // 获取cookie中的token
            String token = CookieUtils.getCookieValue(request, userTokenProperties.getCookieName());
            // 使用公钥解析token
            Payload<UserInfo> payload = JwtUtils.getInfoFromToken(token, jwtProperties.getPublicKey(), UserInfo.class);

            // 校验黑名单
            String tokenId = payload.getId();
            Boolean boo = redisTemplate.hasKey(tokenId);
            if (boo != null && boo) {
                // 如果黑名单中有，则说明用户已经注销,直接抛出异常，token解析失败
                throw new LyException(ExceptionEnum.UNAUTHORIZED);
            }

            // 解析完  token, 判断过期时间，规定如果还剩 5分钟过期，则重新生成
            Date expiration = payload.getExpiration(); // 从生成token开始1:00 ，往后数30分钟的时间1:30。失效点 1:30
            DateTime expirationTime = new DateTime(expiration.getTime());// 使用 DateTime 转换为Long，用于时间的计算
            // 将   失效点 - 最小token刷新时间间隔   得到一个token的   最晚刷新点， 1:30 - 5分钟 = 1:25
            DateTime updateTokenTime = expirationTime.minusMinutes(userTokenProperties.getMinRefreshInterval());
            // 比对 当前时间 是否 大于 最晚刷新点，如果超过了最晚刷新点，说明离token过期已经不剩5分钟了。1:25 和当前比
            if (updateTokenTime.isBefore(System.currentTimeMillis())) {
                // 如果刷新点1:25 在 当前时间的前边(比如1:27)，则重新生成token
                // 从 原token中 取出 userInfo
                UserInfo userInfo = payload.getUserInfo();
                // 根据私钥生成token
                createToken(response, userInfo);
            }
            // 解析成功，返回 Payload 中的 UserInfo
            return payload.getUserInfo();
        } catch (Exception e) {
            // token过期  解析失败 抛出异常
            // 抛出异常，证明token无效，直接返回401
            throw new LyException(ExceptionEnum.UNAUTHORIZED);
        }
    }

    /**
     * 根据 私钥生成token
     *
     * @param response
     * @param userInfo
     */
    private void createToken(HttpServletResponse response, UserInfo userInfo) {
        String token = JwtUtils.generateTokenExpireInMinutes(userInfo,
                jwtProperties.getPrivateKey(),
                userTokenProperties.getExpire());

        // 将token设置到cookie中，需要response
        CookieUtils.newCookieBuilder()
                .name(userTokenProperties.getCookieName())
                .value(token)
                .httpOnly(true)// 保证安全防止XSS攻击，不允许JS操作cookie
                .domain(userTokenProperties.getCookieDomain())
                .response(response)
                .build();
    }

    @Autowired
    private StringRedisTemplate redisTemplate;

    public void logout(HttpServletRequest request, HttpServletResponse response) {
        // 获取token
        String token = CookieUtils.getCookieValue(request, userTokenProperties.getCookieName());
        // 解析token
        Payload<UserInfo> payload = JwtUtils.getInfoFromToken(token, jwtProperties.getPublicKey(), UserInfo.class);
        // 将当前token 放入 redis，黑名单
        /**
         * 计算token在黑名单中存多久---》
         * 在token有效期之内，一直放在黑名单，token有效期过了之后，redis就不需要存了
         * 也就是需要redis设置有效期
         */
        // 计算token还有多长时间过期
        // token有效期 -  当前时间 = 剩余有效期，作为redis失效时间
        long time = payload.getExpiration().getTime() - System.currentTimeMillis();
        String tokenId = payload.getId();
        if (time > 5000) {
            // 如果剩余时间大于五秒，就存，否则就没必要存了
            redisTemplate.opsForValue().set(tokenId, tokenId, time, TimeUnit.MILLISECONDS);
        }
        // 删除cookie，写入 response
        CookieUtils.deleteCookie(userTokenProperties.getCookieName(), userTokenProperties.getCookieDomain(), response);
    }
}
